package marcelvm;

import datastructures.LocalsTable;
import datastructures.SystemHeap;
import datastructures.SystemStack;
import java.util.ArrayList;
import java.util.List;

/**
 * Konkretni kontext interpreteru
 * @author honzee
 */
public class InterpreterContext {

    private final SystemStack stack;
    private final LocalsTable table;
    private static SystemHeap heap;
    private int position;

    public InterpreterContext(Integer stackSize, Integer labelSize, 
            int position) {
        this.stack = new SystemStack(stackSize);
        this.table = new LocalsTable(labelSize);
        this.position = position;
        if (heap == null) {
            heap = new SystemHeap();
        }
    }

    /**
     * Vraci pozici
     */
    public int getPosition() {
        return position;
    }

    /**
     * Nastavi pozici
     */
    public void setPosition(int position) {
        this.position = position;
    }

    /**
     * lds
     * Ulozi string na heapu a ukazatel
     * na nej na system stack
     */
    public void lds(String str) {
        Integer pointer = heap.putString(str);
        stack.push(pointer);
    }

    /**
     * ldc
     * Ulozi hodnotu na system stack
     */
    public void ldc(Integer value) {
        stack.push(value);
    }

    /**
     * store
     * Ulozi hodnotu z vrcholu zasobniku na zadany slot v locals table
     */
    public void store(Integer slot) {
        Integer a = stack.pop();
        table.put(slot, a);
    }

    /**
     * load
     * Ulozi hodnotu ze zadaneho slotu v locals table na vrchol zasobniku
     */
    public void load(Integer slot) {
        Integer a = table.get(slot);
        stack.push(a);
    }

    /**
     * astore
     * Ulozi hodnotu do pole
     */
    public void astore(Integer slot) {
        // pocet dimenzi
        Integer dimensionCount = stack.pop();
        // souradnice v poli
        int[] dimensions = new int[dimensionCount];
        for (int i = dimensionCount-1; i >= 0; i--) {
            dimensions[i] = stack.pop();
        }
        // hodnota, kterou budeme ukladat
        Integer value = stack.pop();
        // pointer do heapy
        Integer pointer = table.get(slot);

        heap.putValueIntoArray(pointer, dimensions, value);
    }

    /**
     * aload
     * Ulozi hodnotu z pole na vrchol zasobniku
     */
    public void aload(Integer slot) {
        // pocet dimenzi
        Integer dimensionCount = stack.pop();
        // souradnice v poli
        int[] dimensions = new int[dimensionCount];
        for (int i = dimensionCount-1; i >= 0; i--) {
            dimensions[i] = stack.pop();
        }
        // pointer do heapy
        Integer pointer = table.get(slot);
        // nacteni hodnoty z pole
        Integer value = heap.getValueFromArray(pointer, dimensions);

        stack.push(value);
    }

    /**
     * convert
     * Ulozi hodnotu z vrcholu zasobniku
     * jako string na heapu
     */
    public void convert() {
        Integer a = stack.pop();
        Integer pointer = heap.putString(String.valueOf(a));
        stack.push(pointer);
    }

    /**
     * write
     * Vytiskne vrchni hodnotu ze zasobniku
     * na standardni vystup nebo do souboru
     */
    public void write(Integer where) throws Exception {
        Integer pointer = stack.pop();
        String a = heap.getString(pointer);
        if (where == 0) {
            System.out.print(a + " ");
        } else if (where == 1) {
            pointer = stack.pop();
            String file = heap.getString(pointer);
            IOLibrary.write(a + " ", file);
        }
    }

    /**
     * writeln
     * Vytiskne vrchni hodnotu ze zasobniku
     * na st. vystup nebo do souboru a prida zalomeni radku
     */
    public void writeln(Integer where) throws Exception {
        Integer pointer = stack.pop();
        String a = heap.getString(pointer);
        if (where == 0) {
            System.out.println(a);
        } else if (where == 1) {
            pointer = stack.pop();
            String file = heap.getString(pointer);
            IOLibrary.write(a + "\n", file);
        }
    }

    /**
     * iread
     * Nacte integer ze souboru a ulozi ho
     * na vrchol zasobniku
     */
    public void iread() throws Exception {
        Integer pointer = stack.pop();
        String file = heap.getString(pointer);
        stack.push(IOLibrary.readInt(file));
    }

    /**
     * of
     * Otevre soubor pro cteni nebo zapis
     */
    public void of(Integer rw) throws Exception {
        Integer pointer = stack.pop();
        String file = heap.getString(pointer);
        if (rw == 0) {
            IOLibrary.openFileForRead(file);
        } else if (rw == 1) {
            IOLibrary.openFileForWrite(file);
        }
    }

    /**
     * cf
     * Zavre soubor
     */
    public void cf() throws Exception {
        Integer pointer = stack.pop();
        String file = heap.getString(pointer);
        IOLibrary.closeFile(file);
    }

    /**
     * iadd
     * Odebere dve int hodnoty ze stacku a ulozi na nej jejich soucet
     */
    public void iadd() {
        int b = stack.pop();
        int a = stack.pop();
        stack.push(a + b);
    }

    /**
     * sadd
     * Odebere dve string hodnoty ze stacku a ulozi na nej jejich spojeni
     */
    public void sadd() {
        // nacteme prvni hodnotu
        Integer pointer = stack.pop();
        String b = heap.getString(pointer);
        // nacteme druhou hodnotu
        pointer = stack.pop();
        String a = heap.getString(pointer);
        // spojime a ulozime
        pointer = heap.putString(a.concat(b));
        stack.push(pointer);
    }

    /**
     * isub
     * Odebere dve int hodnoty ze stacku a ulozi na nej jejich rozdil
     */
    public void isub() {
        int b = stack.pop();
        int a = stack.pop();
        stack.push(a - b);
    }

    /**
     * imul
     * Odebere dve int hodnoty ze stacku a ulozi na nej jejich soucin
     */
    public void imul() {
        int b = stack.pop();
        int a = stack.pop();
        stack.push(a * b);
    }

    /**
     * idiv
     * Odebere dve int hodnoty ze stacku a ulozi na nej jejich podil
     */
    public void idiv() {
        int b = stack.pop();
        int a = stack.pop();
        stack.push(a / b);
    }

    /**
     * newarray
     * Alokuje na heape nove pole
     */
    public void newarray(Integer dimensionCount) {
        int[] dimensions = new int[dimensionCount];
        for (int i = dimensionCount-1; i >= 0; i--) {
            dimensions[i] = stack.pop();
        }
        Integer pointer = heap.putArray(dimensions);
        stack.push(pointer);
    }

    /**
     * dealloc
     * Dealokuje misto na halde
     */
    public void dealloc() {
        Integer pointer = stack.pop();
        heap.dealloc(pointer);
    }

    public boolean eq() {
        int b = stack.pop();
        int a = stack.pop();
        return a == b;
    }

    public boolean ne() {
        int b = stack.pop();
        int a = stack.pop();
        return a != b;
    }

    public boolean ge() {
        int b = stack.pop();
        int a = stack.pop();
        return a >= b;
    }

    public boolean le() {
        int b = stack.pop();
        int a = stack.pop();
        return a <= b;
    }

    public boolean gt() {
        int b = stack.pop();
        int a = stack.pop();
        return a > b;
    }

    public boolean lt() {
        int b = stack.pop();
        int a = stack.pop();
        return a < b;
    }

    /**
     * invoke
     * Nacte pozadovany pocet argumentu a poskytne je novemu kontextu
     */
    public List<Integer> invoke(Integer valueCount) {
        List<Integer> args = new ArrayList<Integer>();
        for (int i = 0; i < valueCount; i++) {
           args.add(stack.pop());
        }
        return args;
    }

    /**
     * ireturn
     * Vraci hodnotu ze stacku
     */
    public Integer ireturn() {
        return stack.pop();
    }
}
